package net.comfycastle.musicplayer;

import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.ListIterator;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ContentResolver;
import android.content.Intent;
import android.database.Cursor;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.IBinder;
import android.os.PowerManager;
import android.provider.MediaStore.Audio;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.widget.RemoteViews;

/**
 * 과연 별도의 스레드가 필요할까
 * 별도의 스레도를 사용하면 로딩이 끝난 정확한 시점을 알 수 없고,
 * 그렇다면 음악을 시작할 때 오류가 날 가능성이 생긴다.
 * @author ricale
 *
 */
public class MusicService extends Service {
	///////////
	//////////
	/////////   enum
	
	/**
	 * 음악 재생의 반복 상태를 정의한다.
	 *
	 */
	public static enum REPEAT {
		/** 음악 재생 반복을 하지 않을 때 */
		NONE,
		/** 음악 재생 반복을 한 곡만 대상으로 할 때 */
		ONE,
		/** 음악 재생 반복을 리스트 전체로 할 때 */
		LIST
	} // end REPEAT
	
	/**
	 * 랜덤 재생 시 다음곡/이전곡 재생 중 가장 최근에 실행한 명령이 무엇인지를 정의한다.
	 * 이는 이전에 실행된 메서드가 listIterator.previous()/listIterator.next() 둘 중  무엇이었는지에 따라 동작 형태가 다른 
	 * listIterator.previous()/listIterator.next() 각각의 메서드드들을 효율적으로 활용하기 위함이다.
	 *
	 */
	private static enum ITER_LASTMOVE {
		/** 자장 최근의 명령이 이전곡 재생이었다. */
		PREVIOUS,
		/** 자장 최근의 명령이 다음곡 재생이었다. */
		NEXT
	} // end ITER_LASTMOVE
	
	///////////
	//////////
	/////////   constant
	
	/** 재생 목록의 끝에 다달았을 때를 의미하는 인덱스값 */
	private static final int IDX_END = -1;
	
	/** 의도하지 않은 상황(에러 상황)에서의 리턴값으로 쓰인다. */
	private static final String ERROR_STR = "ERROR";
	/** 의도하지 않은 상황(에러 상황)에서의 리턴값으로 쓰인다. */
	private static final int ERROR_INT = -1;
	
	///////////
	//////////
	/////////   variable
	
	/** 컨텐트 리졸버 객체 */
	private ContentResolver mCr;
	/** 재생될 음악 목록을 가리키는 cursor */
	private Cursor mCursor;
	/** 미디어 플레이어 객체 */
	private MediaPlayer mPlayer;
	
	/** 목록에서 현재 재생되고 있는 음악의 인덱스 */
	private int mIdx;
	
	/** 이 서비스가 이용되고 있는가 (음악이 재생되고 있는가) */
	private boolean mIsUsing;
	/** 이 노래는 재생되고 있었는가 (다음곡 재생, 노래 재생 위치 변경 등에서 해당 작업이 끝나면 곧바로 재생할 것인가를 판단하기 위해서) */
	private boolean mWasPlaying;
	/** 리스트를 반복 재생하는가 */
	private REPEAT mRepeatState;
	
	/** 리스트를 임의의 순서로 반복하는가 */
	private boolean mIsRandom;
	/** 현재 목록의 총 곡 수 */
	private int mCountOfList;
	/** 각각의 곡들은 재생된 적이 있는가. 랜덤 재생 시에만 쓰인다. */
	private LinkedList<Integer> mRandomListNotPlayed;
	/** 각각의 곡들은 재생된 적이 있는가. 랜덤 재생 시에만 쓰인다. */
	private LinkedList<Integer> mRandomList;
	/** 현재 랜덤 리스트 iterator */
	private ListIterator<Integer> mRandomListIter;
	/** iterator의 마지막 동작 */
	private ITER_LASTMOVE mLastMove;
	/** 인덱스가 변경중인 동작에 있는가. 스레드로 인한 충돌 (특히 랜덤재생 시) 방지를 목적으로 한다. */
	private boolean mIsChangingIndex;
	
	/** 노래가 재생될 때 나타낼 통지 객체 */
	private Notification mNotification;
	/** 통지 관리 객체 */
	private NotificationManager mNotiMgr;
	/** 통지 레이아웃 뷰 */
	private RemoteViews mNotiRView;
	
	/** 통지가 필요한 상태인가 */
	private boolean mIsNeedNotificate;
	
	///////////
	//////////
	/////////   lifecycle callback method
	
	/**
	 * 서비스가 생성될 때의 콜백 메서드
	 */
	public void onCreate() {
		super.onCreate();
		Log.i("SERVICE", "onCreate");
		
		mPlayer = new MediaPlayer();
		mCr     = getContentResolver();
		
	    mPlayer.setOnCompletionListener(mOnCompletion);
	    mPlayer.setOnSeekCompleteListener(mOnSeekComplete);
	    mPlayer.setOnErrorListener(mOnError);
	    mPlayer.setOnPreparedListener(mOnPrepared);
	    mPlayer.setOnVideoSizeChangedListener(mVideoSizeChangedListener);
	    mPlayer.setWakeMode(MusicService.this, PowerManager.PARTIAL_WAKE_LOCK);
	    
	    mIsUsing = false;
	    
	    mNotiMgr = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
	    
	    Intent intent = new Intent(this, PlayActivity.class);
		intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
		intent.setAction(MPApp.ACT_SHOW_PLAYING);
		
		PendingIntent content = PendingIntent.getActivity(this, 0, intent, 0);
		
		mNotiRView = new RemoteViews(getPackageName(), R.layout.notification);
		
		mNotification = new NotificationCompat.Builder(this).setContent(mNotiRView)
				                                            .setContentIntent(content)
				                                            .setOngoing(true)
				                                            .setAutoCancel(true)
				                                            .setSmallIcon(R.drawable.ic_launcher)
				                                            .build();
		
		mRepeatState = REPEAT.LIST;
		mIsRandom    = true;
	} // end onCreate
	
	/**
	 * 서비스가 실행될 때의 콜백 메서드.
	 * 별도의 스레드를 만들어 동작한다.
	 */
	@Override
	public int onStartCommand(final Intent intent, int flags, int startId) {
		super.onStartCommand(intent, flags, startId);
		Log.i("SERVICE", "onStartCommand");
		
		if(intent != null) {
		
			new Thread(new Runnable() { 
				public void run() {
					
					mIsNeedNotificate = false;
					mRandomListNotPlayed = null;
					
					MusicListInfo mli = intent.getParcelableExtra(MPApp.ITT_MUSICLISTINFO);
					
					String   where = mli.getWhere();		
					String[] columns = { MPApp.TAG_ID, MPApp.TAG_FILEPATH, MPApp.TAG_TITLE, MPApp.TAG_ALBUM, MPApp.TAG_ARTIST, MPApp.TAG_ALBUMID };
					String   orderby = mli.getOrderby();
					
					if(mCursor != null) {
						mCursor.close();
					} // end if
				    mCursor = mCr.query(Audio.Media.EXTERNAL_CONTENT_URI, columns, where, null, orderby);
				    mCountOfList = mCursor.getCount();
				    
				    mPlayer.reset();
				    mIdx = mli.getStart();
					
				    if(load(mIdx)) {
				    	mPlayer.start();
				    } // end if
					
				} // end run
			}).start(); // end Thread 
			
		} else {
			Log.i("MUSICSERVICE", "Intent is null");
		} // end if
		
		return START_STICKY;
	} // end onStartCommand
	
	/**
	 * 서비스의 메서드들을 어플리케이션이 바인딩할 때의 콜백 메서드
	 */
	public IBinder onBind(Intent intent) {
		Log.i("SERVICE", "onBind");
		mIsUsing = true;
		return mBinder;
	} // end onBind
	
	/**
	 * 서비스가 파괴될 때의 콜백 메서드
	 */
	@Override
	public void onDestroy() {
		Log.i("SERVICE", "onDestroy");
		super.onDestroy();
		
		if(mPlayer != null) {
			mPlayer.release();
			mPlayer = null;
		} // end if
		
		if(mCursor != null) {
			mCursor.close();
		} // end if
	} // end onDestroy
	
	///////////
	//////////
	/////////   methods
	
	/**
	 * 노래를 로딩한다.
	 * @param index 목록 중 재생할 노래의 인덱스
	 * @return 로딩에 성공하면 true, 아니면 false
	 */
	private boolean load(int index) {
		Log.i("SERVICE", "load");
		mCursor.moveToPosition(index);
		String path = mCursor.getString(mCursor.getColumnIndex(MPApp.TAG_FILEPATH));
		Uri uri = Uri.fromFile(new File(path));
		try {
	    	mPlayer.setDataSource(this, uri);
	    } catch(IllegalArgumentException e) {
	    	e.printStackTrace();
			return false;
		} catch(IllegalStateException e) {
			e.printStackTrace();
			return false;
		} catch(SecurityException e) {
			e.printStackTrace();
			return false;
		} catch(IOException e) {
			e.printStackTrace();
			return false;
		} // end try-catch
		
		if(!prepare()) {
			return false;
		} // end if
		
		if(mIsUsing) {
			Intent intent = new Intent();
			intent.setAction(MPApp.BR_MEDIAPLAYER_PREPARE);
			intent.putExtra(MPApp.ITT_ALBUMID, Long.parseLong(mCursor.getString(mCursor.getColumnIndex(MPApp.TAG_ALBUMID))));
			sendBroadcast(intent);
		} // end if
		
		if(mIsNeedNotificate) {
			_setNotification();
		} // end if
		
		return true;
	} // end load
	
	/**
	 * 노래 재생을 준비한다.
	 * @return 성공하면 true, 아니면 false
	 */
	private boolean prepare() {
		Log.i("SERVICE", "prepare");
		try {
			mPlayer.prepare();
		} catch(IllegalStateException e) {
			e.printStackTrace();
			return false;
		} catch(IOException e) {
			e.printStackTrace();
			return false;
		} // end try-catch
		
		return true;
	} // end prepare
	
	/**
	 * 음악이 재생되고 있는가.
	 * MediaPlayer가 null이면 false를 리턴하기 때문에, 같은 if문에서 재차 확인할 필요는 없다.
	 * @return 재생 중이라면 true, 재생 중이 아니거나 MediaPlayer가 null이라면 false
	 */
	private boolean _isPlaying() {
		Log.i("SERVICE", "_isPlaying");
		if(mPlayer != null) {
			return mPlayer.isPlaying();
		} // end if
		
		return false;
	} // end _isPlaying
	
	/**
	 * 다음 곡의 인덱스를 리턴한다.
	 * 현재 반복 재생인가/아닌가, 임의 순서 재생인가/아닌가에 따라 적절한 인덱스를 리턴한다.
	 * 반복 재생 상태가 아니었다면 목록이 끝났을 때 IDX_END를 리턴하며,
	 * 현재 인덱스가 IDX_END일 때는 목록의 첫번째 곡의 인덱스를 리턴한다.
	 * 단, 리턴하는 값은 동시에 mIdx에도 저장이 됨으로 리턴되는 값을 사용하지 않고 호출 후 mIdx를 사용해도 효과는 같다.
	 * @return 다음 곡의 인덱스
	 */
	private int _next() {
		Log.i("SERVICE", "_next");
		if(mIsRandom) {
			if(mRandomListNotPlayed == null) {
				mRandomListNotPlayed = new LinkedList<Integer>();
				
				for(int i = 0; i < mCountOfList; i++) {
					if(i != mIdx) {
						mRandomListNotPlayed.add(Integer.valueOf(i));
					} // end if
				} // end for
				
				mRandomList = new LinkedList<Integer>();
				mRandomList.add(mIdx);
				mRandomListIter = mRandomList.listIterator();
				mRandomListIter.next();
				
				mLastMove = ITER_LASTMOVE.NEXT;
			} else if(mRandomListNotPlayed.size() == 0 && mRepeatState != REPEAT.LIST && mIdx != IDX_END) {
				return mIdx = IDX_END;
			} // end if
			
			if(mLastMove == ITER_LASTMOVE.PREVIOUS) {
				mRandomListIter.next();
				mLastMove = ITER_LASTMOVE.NEXT;
			} // end if
			
			if(!mRandomListIter.hasNext()) {
				if(mRandomListNotPlayed.size() != 0) {
					int n = (int)(Math.random() * mRandomListNotPlayed.size());
					int idx = mRandomListNotPlayed.get(n);
					mRandomListNotPlayed.remove(n);
					
					mRandomListIter.add(idx);
					return mIdx = idx;
					
				} else {
					mRandomListIter = mRandomList.listIterator();
				} // end if
			} // end if
			mIdx = mRandomListIter.next();
			return mIdx;
		} else {
			if(mIdx == IDX_END) {
				return mIdx = 0;
			} // end if
			
			if(mIdx + 1 >= mCountOfList && mRepeatState == REPEAT.LIST) {
				return mIdx = 0;
			} else if(mIdx + 1 >= mCountOfList && mRepeatState != REPEAT.LIST) {
				return mIdx = IDX_END;
			} // end if
			return mIdx += 1;
			
		} // end if
	} // end _next
	
	/**
	 * 이전 곡의 인덱스를 리턴한다.
	 * 현재 반복 재생인가/아닌가, 임의 순서 재생인가/아닌가에 따라 적절한 인덱스를 리턴한다.
	 * 반복 재생 상태가 아니었다면 목록이 끝났을 때 IDX_END를 리턴하며,
	 * 현재 인덱스가 IDX_END일 때는 목록의 마지막 곡의 인덱스를 리턴한다.
	 * 단, 리턴하는 값은 동시에 mIdx에도 저장이 됨으로 리턴되는 값을 사용하지 않고 호출 후 mIdx를 사용해도 효과는 같다.
	 * @return 다음 곡의 인덱스
	 */
	private int _prev() {
		Log.i("SERVICE", "_prev");
		if(mIsRandom) {
			if(mRandomListNotPlayed == null) {
				mRandomListNotPlayed = new LinkedList<Integer>();
				
				for(int i = 0; i < mCountOfList; i++) {
					if(i != mIdx) {
						mRandomListNotPlayed.add(Integer.valueOf(i));
					} // end if
				} // end for
				
				mRandomList = new LinkedList<Integer>();
				mRandomList.add(mIdx);
				mRandomListIter = mRandomList.listIterator();
				
				mLastMove = ITER_LASTMOVE.PREVIOUS;
			} // end if
			
			if(mLastMove == ITER_LASTMOVE.NEXT) {
				mRandomListIter.previous();
				mLastMove = ITER_LASTMOVE.PREVIOUS;
			} // end if
			
			if(!mRandomListIter.hasPrevious()) {
				if(mRandomListNotPlayed.size() != 0) {
					int n = (int)(Math.random() * mRandomListNotPlayed.size());
					int idx = mRandomListNotPlayed.get(n);
					mRandomListNotPlayed.remove(n);
					
					mRandomListIter.add(idx);
				} else {
					mRandomListIter = mRandomList.listIterator(mRandomList.size() - 1);
					mIdx = mRandomList.get(mRandomList.size() - 1);
					return mIdx;
				} // end if
			} // end if
			
			mIdx = mRandomListIter.previous();
			return mIdx;
			
		} else {
			if(mIdx == IDX_END) {
				return mIdx = mCountOfList - 1;
			} // end if
			
			return mIdx = mIdx<0 ? mCountOfList-1 : mIdx-1;
		} // end if
	} // end _next
	
	/**
	 * 통지를 설정한다.
	 */
	private void _setNotification() {
		Log.i("SERVICE", "_setNotification");
		mCursor.moveToPosition(mIdx);
		String title = mCursor.getString(mCursor.getColumnIndex(MPApp.TAG_TITLE));
		mNotiRView.setTextViewText(R.id.notification_main, title);
		
		mNotiMgr.notify(MPApp.NOTI_PLAYING, mNotification);
	} // end _setNotification
	
	/**
	 * 통지를 취소한다.
	 */
	private void _cancelNotification() {
		Log.i("SERVICE", "_cancelNotification");
		mNotiMgr.cancel(MPApp.NOTI_PLAYING);
	} // end _cancelNotification	
	
	///////////
	//////////
	/////////   bind-able methods for another application component
	
	/**
	 * 어플리케이션에 바인딩될 메서드들을 갖고 있는 객체
	 */
	IMusicService.Stub mBinder = new IMusicService.Stub() {
		///////////
		//////////
		/////////   setter
		
		/**
		 * 노래가 재생 중이었는지 저장한다.
		 */
		public void setWasPlaying() {
			Log.i("SERVICE", "setWasPlaying");
			mWasPlaying = _isPlaying();
		} // end setWasPlaying
		
		///////////
		//////////
		/////////   getter
		
		/**
		 * 재생 중인 (혹은 재생 순서인) 노래의 제목을 얻는다.
		 */
		public String getTitle() {
			Log.i("SERVICE", "getTitle");
			if(mCursor != null) {
				mCursor.moveToPosition(mIdx);
				return mCursor.getString(mCursor.getColumnIndex(MPApp.TAG_TITLE));
			} // end if
			
			return ERROR_STR;
		} // end getTitle
		
		/**
		 * 재생 중인 (혹은 재생 순서인) 노래의 앨범 제목을 얻는다.
		 */
		public String getAlbum() {
			Log.i("SERVICE", "getAlbum");
			if(mCursor != null) {
				mCursor.moveToPosition(mIdx);
				return mCursor.getString(mCursor.getColumnIndex(MPApp.TAG_ALBUM));
			} // end if
			
			return ERROR_STR;
		} // end getAlbum
		
		/**
		 * 재생 중인 (혹은 재생 순서인) 노래의 아티스트 이름을 얻는다.
		 */
		public String getArtist() {
			Log.i("SERVICE", "getArtist");
			if(mCursor != null) {
				mCursor.moveToPosition(mIdx);
				return mCursor.getString(mCursor.getColumnIndex(MPApp.TAG_ARTIST));
			} // end if
			
			return ERROR_STR;
		} // end getArtist
		
		/**
		 * 재생 중인 (혹은 재생 순서인) 노래의 총 재생 시간을 얻는다.
		 */
		public int getDuration() {
			Log.i("SERVICE", "getDuration");
			if(mPlayer != null) {
				return mPlayer.getDuration();
			} // end if
			
			return ERROR_INT;
		} // end getDuration
		
		/**
		 * 재생 중인 (혹은 재생 순서인) 노래의 현재 재생 지점을 얻는다.
		 */
		public int getProgress() {
			Log.i("SERVICE", "getProgress");
			if(mPlayer != null) {
				return mPlayer.getCurrentPosition();
			} // end if
			
			return ERROR_INT;
		} // end getProgress
		
		/**
		 * 노래 재생을 위한 준비가 되어 있는가.
		 */
		public boolean isReady() {
			Log.i("SERVICE", "isReady");
			if(mPlayer != null) {
				return true;
			} // end if
			
			return false;
		} // end isReady
		
		/**
		 * 노래를 재생 중인가
		 */
		public boolean isPlaying() {
			Log.i("SERVICE", "isPlaying");
			return _isPlaying();			
		} // end isPlaying
		
		/**
		 * 노래가 재생 중이었나.
		 */
		public boolean wasPlaying() {
			Log.i("SERVICE", "wasPlaying");
			return mWasPlaying;
		} // end wasPlaying
		
		/**
		 * 현재 랜덤 재생중인가
		 */
		public boolean isRandom() {
			Log.i("SERVICE", "isRandom");
			return mIsRandom;
		} // end isRandom
		
		/**
		 * 현재 반복 재생 상태는 어떠한가
		 */
		public int getRepeatState() {
			Log.i("SERVICE", "getRepeatState");
			if(mRepeatState != null) {
				return mRepeatState.ordinal();
			} // end if
			
			return ERROR_INT;
		} // end getRepeatState
		
		///////////
		//////////
		/////////   methods
		
		/**
		 * 노래를 재생한다.
		 * 별도의 스레드를 생성해 동작한다.
		 */
		public void play() {
			Log.i("SERVICE", "play");
			new Thread(new Runnable() {
				public void run() {
					
					if(_isPlaying()) {
						mPlayer.start();
					} // end if
					
				} // end run
			}).start(); // end Thread
		} // end play
		
		/**
		 * 재생을 일시 정지한다.
		 * 별도의 스레드를 생성해 동작한다.
		 */
		public void pause() {
			Log.i("SERVICE", "pause");
			new Thread(new Runnable() {
				public void run() {
					
					if(_isPlaying()) {
						mPlayer.pause();
					} // end if
					
				} // end run
			}).start(); // end Thread
		} // end pause
		
		/**
		 * 재생을 멈춘다.
		 * 별도의 스레드를 생성해 동작한다.
		 */
		public void stop() {
			Log.i("SERVICE", "stop");
			new Thread(new Runnable() {
				public void run() {
					
					if(mPlayer != null) {
						mPlayer.stop();
						prepare();
					} // end if
					
				} // end run
			}).start(); // end Thread
		} // end stop
		
		/**
		 * 재생 순서를 다음 곡으로 넘긴다.
		 * 별도의 스레드를 생성해 동작한다.
		 */
		public void next() {
			Log.i("SERVICE", "next");
			new Thread(new Runnable() {
				public void run() {
					
					if(mPlayer != null && !mIsChangingIndex) {
						mIsChangingIndex = true;
						final boolean wasPlaying = _isPlaying();
						
						mPlayer.reset();
						
						if((mIdx = _next()) == IDX_END) {
							mIdx = _next();
						} // end if
						
						if(load(mIdx)) {
							if(wasPlaying) {
								play();
							} // end if
						} // end if
						mIsChangingIndex = false;
					} // end end if
					
				} // end run
			}).start(); // end Thread
			
		} // end next
		
		/**
		 * 재생 순서를 이전 곡으로 넘긴다. 
		 * 별도의 스레드를 생성해 동작한다.
		 */
		public void prev() {
			Log.i("SERVICE", "prev");
			new Thread(new Runnable() {
				public void run() {
					
					if(mPlayer != null && !mIsChangingIndex) {
						mIsChangingIndex = true;
						final boolean wasPlaying = _isPlaying();
						
						mPlayer.reset();
						
						if((mIdx = _prev()) == IDX_END) {
							mIdx = _prev();
						} // end if
						
						if(load(mIdx)) {
							if(wasPlaying) {
								play();
							} // end if
						} // end if
						mIsChangingIndex = false;
					} // end end if
					
				} // end end run
			}).start(); // end Thread
			
		} // end prev
		
		/**
		 * 재생되는 노래의 재생 지점을 조절한다.
		 * 별도의 스레드를 생성해 동작한다.
		 * @param msec 노래의 재생 지점 (millisecond)
		 */
		public void seekTo(final int msec) {
			Log.i("SERVICE", "seekTo");
			new Thread(new Runnable() {
				public void run() {
					
					if(mPlayer != null) {
						mPlayer.seekTo(msec);
					} // end if
					
				} // end run
			}).start(); // end Thread
		} // end seekTo
		
		/**
		 * 노래 재생 순서를 임의인지 순차인지 토글한다.
		 * 별도의 스레드를 생성해 동작... 하려고 했으나
		 * 동작 자체가 단순한 대입이라 스레드의 필요가 크지 않고
		 * 스레드로 동작하려면 getter를 고려해 락에 관련된 처리를 해주어야 하기 때문에
		 * 별도의 스레드를 생성하지 않는다.
		 * 추후 문제가 생길 시에 수정.
		 */
		public void toggleRandom() {
			Log.i("SERVICE", "toggleRandom");
		//	new Thread(new Runnable() {
		//		public void run() {
					
					mIsRandom = !mIsRandom;
					if(!mIsRandom) {
						mRandomListNotPlayed = null;
					} // end if
					
		//		}
		//	}).start();
		} // end toggleRandom
		
		/**
		 * 노래 재생 반복이 아닌지 한 곡 반복인지 목록 전체 반복인지 (세가지 상태를)토글한다.
		 * 별도의 스레드를 생성해 동작... 하려고 했으나
		 * 동작 자체가 단순한 대입이라 스레드의 필요가 크지 않고
		 * 스레드로 동작하려면 getter를 고려해 락에 관련된 처리를 해주어야 하기 때문에
		 * 별도의 스레드를 생성하지 않는다.
		 * 추후 문제가 생기면 수정.
		 */
		public void toggleRepeat() {
			Log.i("SERVICE", "toggleRepeat");
		//	new Thread(new Runnable() {
		//		public void run() {
					
					if(mRepeatState != null) {
						switch(mRepeatState) {
						case NONE:
							mRepeatState = REPEAT.ONE;
							break;
						case ONE:
							mRepeatState = REPEAT.LIST;
							break;
						case LIST:
							mRepeatState = REPEAT.NONE;
							break;
						} // end switch
					} // end if
					
		//		}
		//	}).start();
		} // end toggleRepeat
		
		/**
		 * 통지를 설정한다.
		 * 별도의 스레드를 생성해 동작한다.
		 */
		public void setNotification() {
			Log.i("SERVICE", "setNotification");
			new Thread(new Runnable() {
				public void run() {
				
					if(_isPlaying()) {
						mIsNeedNotificate = true;
						_setNotification();
					} // end if
					
				} // end run
			}).start(); // end Thread
		} // end setNotification
		
		/**
		 * 통지를 취소한다.
		 * 별도의 스레드를 생성해 동작한다.
		 */
		public void cancelNotification() {
			Log.i("SERVICE", "cancelNotification");
			new Thread(new Runnable() {
				public void run() {
					
					mIsNeedNotificate = false;
					_cancelNotification();
					
				} // end run
			}).start(); // end Thread
			
		} // end cancelNotification
		
		/**
		 * 서비스의 사용이 끝났음을 알린다.
		 * 주로 서비스를 사용하고 있던 어플리케이션을 종료할 때 사용된다.
		 */
		public void finish() {
			Log.i("SERVICE", "finish");
			mIsUsing = false;
		} // end finish
	}; // end mBinder
	
	///////////
	//////////
	/////////   event listener & callback method
	
	/**
	 * 미디어 플레이어가 재생을 완료했을 때의 이벤트 리스너
	 */
	MediaPlayer.OnCompletionListener mOnCompletion = new MediaPlayer.OnCompletionListener() {
		/**
		 * 미디어 플레이어가 재생을 완료했을 때의 콜백 메서드
		 */
		public void onCompletion(MediaPlayer mp) {
			Log.i("SERVICE", "onCompletion");
			new Thread(new Runnable() {
				public void run() {
					
					mIsChangingIndex = true;
					if(mRepeatState == REPEAT.ONE) {
						mPlayer.start();
						
					} else {
						mPlayer.reset();
						mIdx = _next();
						
						if(mIdx != IDX_END) {
							if(load(mIdx)) {
								mPlayer.start();
							} // end if

						} else {
							// 다시 재생하면 유지하고 있던 임의 순서 리스트는 버리고 새롭게 임의 순서로 실행하라는 의미
							mRandomListNotPlayed = null;
							
							if(!mIsUsing) {
								_cancelNotification();
								stopSelf();
							} // end if
							
						} // end if
					} // end if
					mIsChangingIndex = false;
					
				} // end run
			}).start(); // end Thread
		} // end onCompletion
	}; // end mOnCompletion
	
	/**
	 * 미디어 플레이어의 재생 구간 탐색이 끝났을 때의 이벤트 리스너
	 */
	MediaPlayer.OnSeekCompleteListener mOnSeekComplete = new MediaPlayer.OnSeekCompleteListener() {
		/**
		 * 미디어 플레이어의 재생 구간 탐색이 끝났을 때의 콜백 메서드
		 */
		public void onSeekComplete(MediaPlayer mp) {
			Log.i("SERVICE", "onSeekComplete");
			new Thread(new Runnable() {
				public void run() {
					
					if(mWasPlaying) {
						mPlayer.start();
					} // end if
					
				} // end run
			}).start(); // end Thread
		} // end onSeekComplete
	}; // end mOnSeekComplete
	
	/**
	 * 미디어 플레이어가 에러가 났을 때의 이벤트 리스너
	 */
	MediaPlayer.OnErrorListener mOnError = new MediaPlayer.OnErrorListener() {
		/** 지금은 쓰이지 않는다. */
		public boolean onError(MediaPlayer mp, int what, int extra) {
			Log.i("MusicService", "MediaPlayerError: what=" + what + " extra=" + extra);
			return false;
		} // end onError
	}; // end mOnError
	
	/**
	 * 미디어 플레이어의 재생 준비가 끝났을 때의 이벤트 리스너
	 */
	MediaPlayer.OnPreparedListener mOnPrepared = new MediaPlayer.OnPreparedListener() {
		/** 당장은 쓰이지 않는다. */
		public void onPrepared(MediaPlayer mp) {
			Log.i("SERVICE", "onPrepared");
		} // end onPrepared
	}; // end mOnPrepared

	/**
	 * 사용하지 않는다.
	 * 런타임 에러 방지용.
	 */
	MediaPlayer.OnVideoSizeChangedListener mVideoSizeChangedListener = new MediaPlayer.OnVideoSizeChangedListener() {
		/** 사용하지 않는다. */
		@Override
		public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
			Log.i("SERVICE", "onVideoSizeChanged");
		} // end onVideoSizeChanged
	}; // end mVideoSizeChangedListener
} // end MusicService